/* Copyright (c) 2009-2010, Sean A.O. Harney <sharney3@gmail.com> */

#include "omf.h"

/* prototypes */
EXPR_P _expr_eval(EXPR_P expr, int levels_deep);
EXPR_P _expr_eval_leaf(EXPR_P expr);
EXPR_P _expr_eval_binary(EXPR_P expr, int levels_deep);
EXPR_P _expr_eval_assignment(EXPR_P expr);
EXPR_P _expr_eval_functioncall(EXPR_P expr);


/* recursively evaluate tree starting at root node expr */
/* returns EXPR_P to (for now: leaf' */
EXPR_P expr_eval(EXPR_P expr)
{
    return _expr_eval(expr, 0);
}

/* recursively evaluate tree starting at root node expr */
EXPR_P _expr_eval(EXPR_P expr, int levels_deep)
{
    EXPR_P result = NULL;
    char tabs[10];
    int i;

    for (i = 0; i < levels_deep && i < sizeof(tabs) - 1; i++)
	tabs[i] = '\t';
    tabs[i] = '\0';
    if (expr == NULL)
	error_println("expr_eval(expr = NULL.");	//assert expr not null

    debug2_printf("%sexpr_eval(type=%s, levels_deep=%d) ", tabs,
		  expr_type_to_str(expr->type), levels_deep);
    switch (expr->type)
      {
      case EXPR_LEAF:
	  result = _expr_eval_leaf(expr);
	  break;
      case EXPR_BINARY:
	  result = _expr_eval_binary(expr, levels_deep);
	  break;
      case EXPR_ASSIGNMENT:
	  result = _expr_eval_assignment(expr);
	  break;
      case EXPR_FUNCTIONCALL:
	  result = _expr_eval_functioncall(expr);
	  break;
      default:
	  error_println("expr_eval(): unknown expression type: %d",
			expr->type);
	  return NULL;
      }

    if (result != NULL)
	debug2_printf("%sEvaluates to type: %s, val: %s\n", tabs,
		      leaf_type_to_str(result->u.leaf.type),
		      leaf_val_to_str(result));
    return result;
}

//assumes is EXPR_LEAF
EXPR_P _expr_eval_leaf(EXPR_P expr)
{
    EXPR_P result = NULL;
    debug2_printf("leaf type=%s, val: %s\n",
		  leaf_type_to_str(expr->u.leaf.type),
		  leaf_val_to_str(expr));

    switch (expr->u.leaf.type)
      {
      case LEAF_INT:
      case LEAF_FLOAT:
      case LEAF_STRING:
      case LEAF_BOOL:
	  result = expr;
	  break;
      case LEAF_ID:
	  {
	      char *symname = expr->u.leaf.val.id_val;
	      SYMTBL_ENT_P entry;

	      entry = symbol_tbl_get_entry(glob.symbol_tbl_global, symname);	//all func are in global

	      if (entry == NULL)
		  entry = symbol_tbl_get_entry_variable_fatal(symname);


	      if (entry->type == SYM_TYPE_VARIABLE)
		{
		    switch (entry->u.variable.type)
		      {
		      case VAR_TYPE_INT:
			  {
			      int val = entry->u.variable.rvalue.int_val;
			      result = new_expr_leaf_int(val);
			  }
			  break;
		      case VAR_TYPE_FLOAT:
			  {
			      int val = entry->u.variable.rvalue.float_val;
			      result = new_expr_leaf_float(val);
			  }
			  break;
		      case VAR_TYPE_STRING:
			  {
			      char *val =
				  entry->u.variable.rvalue.string_val;
			      result = new_expr_leaf_string(val);
			  }
			  break;
		      case VAR_TYPE_BOOL:
			  {
			      bool val = entry->u.variable.rvalue.bool_val;
			      result = new_expr_leaf_bool(val);
			  }
			  break;
		      case VAR_TYPE_FUNCTION:
			  {
			      char *func_symname =
				  entry->u.variable.rvalue.function_val;
			      result = new_expr_leaf_id(func_symname);
			  }
			  break;
		      case VAR_TYPE_UNINITIALIZED:
		      default:
			  fatal_error_println("unexpected symbol table var type");	//shouldn't get here?
		      }
	      } else if (entry->type == SYM_TYPE_FUNCTION)
		{
		    result = expr;	//keep it as is
		    break;
		}
	  }
      }
    return result;
}


//assumes is EXPR_BINARY
//NOTE: not EXPR_BINARY does not include assignment opeator (EXPR_ASSIGNMENT is used instead)
EXPR_P _expr_eval_binary(EXPR_P expr, int levels_deep)
{
    BINARY_OP_TYPE op = expr->u.binary_op.op;
    EXPR_P res_lhs, res_rhs;
    EXPR_P result = NULL;

    debug2_printf("op type=%s, ", binary_op_type_to_str(op));

    res_lhs = _expr_eval(expr->lhs, levels_deep + 1);
    if (res_lhs == NULL || res_lhs->type != EXPR_LEAF)
	return NULL;
    res_rhs = _expr_eval(expr->rhs, levels_deep + 1);
    if (res_rhs == NULL || res_rhs->type != EXPR_LEAF)
	return NULL;

    LEAF_TYPE type_lhs, type_rhs;

    type_lhs = res_lhs->u.leaf.type;
    type_rhs = res_rhs->u.leaf.type;

    debug2_printf("LHS type=%s, RHS type=%s\n",
		  leaf_type_to_str(type_lhs), leaf_type_to_str(type_rhs));
    if (type_lhs == type_rhs)	/* no implicit casting for typemixing needed */
      {
	  switch (type_lhs)	/* or type_rhs equally */
	    {
	    case LEAF_INT:
		{
		    int val_lhs = res_lhs->u.leaf.val.int_val;
		    int val_rhs = res_rhs->u.leaf.val.int_val;

		    switch (op)
		      {
		      case BINARY_OP_ADD:
			  result = new_expr_leaf_int(val_lhs + val_rhs);
			  break;
		      case BINARY_OP_SUBTRACT:
			  result = new_expr_leaf_int(val_lhs - val_rhs);
			  break;
		      case BINARY_OP_MULTIPLY:
			  result = new_expr_leaf_int(val_lhs * val_rhs);
			  break;
		      case BINARY_OP_DIVIDE:
			  result = new_expr_leaf_int(val_lhs / val_rhs);
			  break;
		      case BINARY_OP_EQUALITY:
			  result = new_expr_leaf_bool(val_lhs == val_rhs);
			  break;
		      default:
			  error_println
			      ("expr_eval(): unknown int op type: %d", op);
			  return NULL;
		      }
		}
		break;
	    case LEAF_FLOAT:
		{
		    float val_lhs = res_lhs->u.leaf.val.float_val;
		    float val_rhs = res_rhs->u.leaf.val.float_val;

		    switch (op)
		      {
		      case BINARY_OP_ADD:
			  result = new_expr_leaf_float(val_lhs + val_rhs);
			  break;
		      case BINARY_OP_SUBTRACT:
			  result = new_expr_leaf_float(val_lhs - val_rhs);
			  break;
		      case BINARY_OP_MULTIPLY:
			  result = new_expr_leaf_float(val_lhs * val_rhs);
			  break;
		      case BINARY_OP_DIVIDE:
			  result = new_expr_leaf_float(val_lhs / val_rhs);
			  break;
		      case BINARY_OP_EQUALITY:
			  result = new_expr_leaf_bool(val_lhs == val_rhs);
			  break;

		      default:
			  error_println
			      ("expr_eval(): unknown float op type: %d",
			       op);
			  return NULL;
		      }
		}
		break;
	    case LEAF_STRING:
		{
		    char *val_lhs = res_lhs->u.leaf.val.string_val;
		    char *val_rhs = res_rhs->u.leaf.val.string_val;
		    char *new_str;

		    switch (op)
		      {
		      case BINARY_OP_ADD:	/* concat */
			  new_str = checked_malloc(strlen(val_rhs) + strlen(val_lhs) + 1);	//TODO: need to free
			  strcat(new_str, val_lhs);	/* no need for strncat */
			  strcat(new_str, val_rhs);
			  result = new_expr_leaf_string(new_str);	// does not strdup string
			  break;
		      case BINARY_OP_EQUALITY:
			  result = new_expr_leaf_bool(!strcmp(val_lhs, val_rhs));	//do strcmp, not just pointer  compare!
			  break;

		      case BINARY_OP_SUBTRACT:
		      case BINARY_OP_MULTIPLY:
		      case BINARY_OP_DIVIDE:
		      default:
			  error_println
			      ("expr_eval(): unknown string op type: %d",
			       op);
			  return NULL;
		      }
		}
		break;
	    case LEAF_BOOL:
		{
		    bool val_lhs = res_lhs->u.leaf.val.bool_val;
		    bool val_rhs = res_rhs->u.leaf.val.bool_val;

		    switch (op)
		      {
		      case BINARY_OP_EQUALITY:
			  result = new_expr_leaf_bool(val_lhs == val_rhs);
			  break;

		      case BINARY_OP_ADD:
		      case BINARY_OP_SUBTRACT:
		      case BINARY_OP_MULTIPLY:
		      case BINARY_OP_DIVIDE:
		      default:
			  error_println
			      ("expr_eval(): unknown bool op type: %d",
			       op);
			  return NULL;
		      }
		}
		break;

	    case LEAF_ID:
		//TODO:
		fatal_error_println("NO ID YET!  TODO");
		break;
	    default:
		error_println
		    ("expr_eval(): unknown leaf type: %d", type_lhs);
		return NULL;
	    }

    } else			// mixed types binary op
      {
	  if (op == BINARY_OP_EQUALITY)
	      result = new_expr_leaf_bool(false);	// diff types are never equal
	  else
	      return NULL;	// TODO: support typemixing 
      }
    return result;
}


//assumes is EXPR_ASSIGNMENT
    //returns rhs's evaluation (always a leaf expr)
EXPR_P _expr_eval_assignment(EXPR_P expr)
{
    EXPR_P lhs = expr->lhs;
    EXPR_P rhs = expr->rhs;

    //rhs
    if (rhs == NULL)
	fatal_error_println("assignment rhs NULL");	//shouldn't ever get here

    EXPR_P rhs_result = expr_eval(rhs);
    //what will be returned: rhs_result

    if (rhs_result == NULL)
	fatal_error_println("assignment rhs_result NULL");	//shouldn't ever get here

    LEAF_TYPE rhs_res_type = rhs_result->u.leaf.type;

///printf("rhsresult EXPRtype=%s,leaftype=%s\n", expr_type_to_str(rhs_result->type), leaf_type_to_str(rhs_res_type));

//handle lhs
    if (lhs == NULL)
	fatal_error_println("assignment lhs NULL");	//shouldn't ever get here

    if (lhs->type != EXPR_LEAF)
	fatal_error_println("Unexpected assignment lhs  not type EXPR_LEAF");	//shouldn't ever get here

    if (lhs->u.leaf.type != LEAF_ID)
	fatal_error_println("Unexpected assignment lhs leaf is not type LEAF_ID");	//shouldn't ever get here

    char *lhs_symname;

    lhs_symname = lhs->u.leaf.val.id_val;	//id_val is a symname
    SYMTBL_ENT_P entry;
    entry = symbol_tbl_get_entry_variable_fatal(lhs_symname);	//get lhs of assignment from (local) symboltbl

    switch (entry->u.variable.type)
      {
      case VAR_TYPE_INT:
	  if (rhs_res_type != LEAF_INT)
	      fatal_error_println
		  ("Type Mismatch assignment lhs %s INT, rhs %s",
		   lhs_symname, leaf_type_to_str(rhs_res_type));

	  entry->u.variable.rvalue.int_val =
	      rhs_result->u.leaf.val.int_val;
	  break;
      case VAR_TYPE_FLOAT:
	  if (rhs_res_type != LEAF_FLOAT)
	      fatal_error_println
		  ("Type Mismatch assignment lhs %s FLOAT, rhs %s",
		   lhs_symname, leaf_type_to_str(rhs_res_type));

	  entry->u.variable.rvalue.float_val =
	      rhs_result->u.leaf.val.float_val;
	  break;
      case VAR_TYPE_STRING:
	  if (rhs_res_type != LEAF_STRING)
	      fatal_error_println
		  ("Type Mismatch assignment lhs %s STRING, rhs %s",
		   lhs_symname, leaf_type_to_str(rhs_res_type));

	  entry->u.variable.rvalue.string_val =
	      rhs_result->u.leaf.val.string_val;
	  break;
      case VAR_TYPE_BOOL:
	  if (rhs_res_type != LEAF_BOOL)
	      fatal_error_println
		  ("Type Mismatch assignment lhs %s BOOL, rhs %s",
		   lhs_symname, leaf_type_to_str(rhs_res_type));

	  entry->u.variable.rvalue.bool_val =
	      rhs_result->u.leaf.val.bool_val;
	  break;
      case VAR_TYPE_FUNCTION:
	  //TODO: does it get here ever?
	  printf("TP_assignment of VAR_TYPE_FUNCTION **************************************");	//REMOVE
	  if (rhs_res_type != LEAF_ID)	//TODO?? MAYBE
	      fatal_error_println
		  ("Type Mismatch assignment lhs %s FUNCTION, rhs %s",
		   lhs_symname, leaf_type_to_str(rhs_res_type));

	  entry->u.variable.rvalue.string_val =
	      rhs_result->u.leaf.val.id_val;
	  break;

      case VAR_TYPE_UNINITIALIZED:
	  /* var s; s = 3; # 2nd stmt (expr stmt wth assignment expr) has lhs UNINITIALIZED) */
	  switch (rhs_res_type)
	    {
	    case LEAF_INT:
		entry->u.variable.type = VAR_TYPE_INT;	//infer type from rhs
		entry->u.variable.rvalue.int_val =
		    rhs_result->u.leaf.val.int_val;
		break;
	    case LEAF_FLOAT:
		entry->u.variable.type = VAR_TYPE_FLOAT;
		entry->u.variable.rvalue.float_val =
		    rhs_result->u.leaf.val.float_val;
		break;
	    case LEAF_STRING:
		entry->u.variable.type = VAR_TYPE_STRING;
		entry->u.variable.rvalue.string_val =
		    rhs_result->u.leaf.val.string_val;
		break;
	    case LEAF_BOOL:
		entry->u.variable.type = VAR_TYPE_BOOL;
		entry->u.variable.rvalue.bool_val =
		    rhs_result->u.leaf.val.bool_val;
		break;
	    case LEAF_ID:
		{
		    char *rhs_symname = rhs_result->u.leaf.val.id_val;
		    SYMTBL_ENT_P func_entry =
			symbol_tbl_get_entry(glob.symbol_tbl_global,
					     rhs_symname);
		    if (func_entry == NULL)	//all globals are functions
			fatal_error_println("\nAssignment Expr: lhs variable %s , rhs id %s is not a function.", lhs_symname, rhs_symname);	//will happen from programmer error.
		    entry->u.variable.type = VAR_TYPE_FUNCTION;
		    entry->u.variable.rvalue.function_val = rhs_symname;


		}
		break;
	    default:
		fatal_error_println("\nAssignment Expr: lhs variable %s , rhs type not int,float,string,bool,function", lhs_symname);	//shouldn't get here
	    }
	  break;
      default:
	  fatal_error_println("\nAssignment Expr: lhs variable %s : unknown type.", lhs_symname);	//shouldn't get here.
      }
    return rhs_result;		/* value of assignment exp is rhs_result */
}

//assumes is EXPR_FUNCTIONCALL
//NOTE: used in calling main() under the table.
EXPR_P _expr_eval_functioncall(EXPR_P expr)
{
    char *funcname, *actual_funcname;
    SYMTBL_ENT_P entry;
    LLIST_P asl;
    LLIST_P args_expr_list;	//llist of EXPR_P
    LLIST_P walker;
    EXPR_P result[9];
    int argc;
    char param[3] = { '$', '0', '\0' };

    args_expr_list = expr->u.functioncall.args_expr_list;

//funcnme is lexical id from src code
    funcname = expr->u.functioncall.funcname;	//NOTE: funcname here may be a variable (TyPE_FUNCTION) also. funcname is a misnomer!
    EXPR_P expr_id = new_expr_leaf_id(funcname);
    EXPR_P actual_funcname_expr = expr_eval(expr_id);	//evaluate it (it might be a function variable).

    if (actual_funcname_expr->type != EXPR_LEAF
	&& actual_funcname_expr->u.leaf.type != LEAF_ID)
	fatal_error_println
	    ("expr_eval_functioncall: Was expecting leaf id");
    //unknown if it gets here.TODO chk

    actual_funcname = actual_funcname_expr->u.leaf.val.id_val;


    entry = symbol_tbl_get_entry(glob.symbol_tbl_global, actual_funcname);
    if (entry == NULL)
      {
	  if (!strcmp(funcname, "main"))	// empty program is OK. it never had 'main' on symbol tbl. 
	      return NULL;

	  fatal_error_println
	      ("expr_eval_functioncall: Reference to undeclared symbol: %s",
	       actual_funcname);
      }
//NOTE: !SYM_TYPE_FUNCTION => SYM_TYPE_VARIABLE
    if (entry->type != SYM_TYPE_FUNCTION
	&& entry->u.variable.type != VAR_TYPE_FUNCTION)
	fatal_error_println
	    ("id %s is not a function or function typed variable.",
	     actual_funcname);

    /*$1 - $9; evaluated from left to right */
    for (walker = args_expr_list, argc = 1; walker != NULL && argc < 10;
	 walker = walker->next, argc++)
      {
	  EXPR_P tmp = walker->element;
	  result[argc - 1] = expr_eval(tmp);
	  if ((result[argc - 1])->type != EXPR_LEAF)	//shoulnt happen.
	      fatal_error_println("Expected EXPR_LEAF... symbol: %s",
				  funcname);
      }

//expr's were evaluated with old top of stack symtbl.
/* create new local symtbl */
//TODO: free it at func return
    HASHTBL *symbol_tbl_local = symbol_tbl_create();
    symbol_tbl_local_stack_push(symbol_tbl_local);

/* create positional params */
    /* $0 is function name */
    symbol_tbl_new_entry_variable_from_leafexpr(symbol_tbl_local,
						param /* "$0" */ ,
						"" /* no file */ ,
						actual_funcname_expr);


    for (walker = args_expr_list, argc = 1; walker != NULL && argc < 10;
	 walker = walker->next, argc++)
      {
	  (param[1])++;		//increment ASCII 0 to 1...9
//TODO: maybe remove existing?
//this func asserts it is EXPR_LEAF type expr
	  symbol_tbl_new_entry_variable_from_leafexpr(symbol_tbl_local,
						      param /* "$_" */ ,
						      "" /* no file */ ,
						      result[argc - 1]);
	  //TODO: err chk
      }




/* call the function */
    asl = entry->u.function.asl;
    debug2_printf("Processing ASL[%s]\n", funcname);
    asl_process_asl(asl);

    symbol_tbl_local = symbol_tbl_local_stack_pop();	//returns same value as symbol_tbl_local already was
    hashtbl_destroy(symbol_tbl_local);

    EXPR_P ret = glob.current_function_return;	//might be NULL
    glob.current_function_return = NULL;


    return ret;			//already eval'ed. or else is NULL if no return stmt.

}
